/**
 * Environment detection and fallback utilities
 * Provides cross-platform environment detection with intelligent fallbacks
 */

const os = require('os');
const fs = require('fs');
const path = require('path');
const { getConfigDir, getAlternativeConfigDir, ensureDirectory } = require('./platform');
const { ErrorFactory, ErrorHandler } = require('./errors');
const { getLogger } = require('../logging/logger');

const logger = getLogger();

/**
 * Environment configuration defaults
 */
const ENVIRONMENT_DEFAULTS = {
  config_filename: 'settings.json',
  log_filename: 'vision-analyzer.log',
  default_port: 3000,
  fallback_ports: [3001, 3002, 3003, 8080, 8081],
  timeout_default: 30000,
  timeout_max: 300000,
  retry_default: 3,
  retry_max: 10
};

/**
 * Environment detection result
 */
class EnvironmentInfo {
  constructor() {
    this.platform = os.platform();
    this.arch = os.arch();
    this.release = os.release();
    this.node_version = process.version;
    this.memory_total = os.totalmem();
    this.memory_free = os.freemem();
    this.cpucount = os.cpus().length;
    this.home_dir = os.homedir();
    this.temp_dir = os.tmpdir();
    this.working_dir = process.cwd();
    this.config_dir = null;
    this.config_dir_writable = false;
    this.config_file_exists = false;
    this.log_dir = null;
    this.log_dir_writable = false;
    this.http_port_available = null;
    this.env_vars = {};
    this.path_separators = {
      path: path.sep,
      path_env: path.delimiter
    };
  }

  /**
   * Get memory usage as percentage
   * @returns {number} Memory usage percentage
   */
  getMemoryUsagePercent() {
    return ((this.memory_total - this.memory_free) / this.memory_total * 100).toFixed(2);
  }

  /**
   * Check if environment supports required features
   * @returns {Object} Feature support information
   */
  getFeatureSupport() {
    return {
      fs_promises: typeof fs.promises !== 'undefined',
      async_await: true, // Node.js 7.6+ supports async/await
      es6_modules: false, // We're using CommonJS
      worker_threads: false, // Check if needed
      large_files: this.platform === 'win32' || this.platform === 'darwin'
    };
  }

  /**
   * Get system resource limits
   * @returns {Object} Resource limits
   */
  getResourceLimits() {
    const limits = {
      max_memory: this.memory_total,
      recommended_max_file_size: '50MB',
      recommended_concurrent_requests: Math.max(1, Math.floor(this.cpucount / 2)),
      recommended_timeout: 30000
    };

    // Adjust based on available memory
    if (this.memory_total < 1024 * 1024 * 1024) { // < 1GB
      limits.recommended_max_file_size = '10MB';
      limits.recommended_timeout = 60000;
    } else if (this.memory_total < 2 * 1024 * 1024 * 1024) { // < 2GB
      limits.recommended_max_file_size = '25MB';
    }

    return limits;
  }

  /**
   * Convert to JSON object
   * @returns {Object} Environment information
   */
  toJSON() {
    return {
      platform: this.platform,
      arch: this.arch,
      release: this.release,
      node_version: this.node_version,
      memory: {
        total: this.memory_total,
        free: this.memory_free,
        usage_percent: this.getMemoryUsagePercent()
      },
      cpu: {
        count: this.cpucount
      },
      paths: {
        home: this.home_dir,
        temp: this.temp_dir,
        working: this.working_dir,
        config: this.config_dir,
        log: this.log_dir,
        separators: this.path_separators
      },
      access: {
        config_dir_writable: this.config_dir_writable,
        config_file_exists: this.config_file_exists,
        log_dir_writable: this.log_dir_writable
      },
      networking: {
        http_port_available: this.http_port_available
      },
      features: this.getFeatureSupport(),
      limits: this.getResourceLimits(),
      env_vars: this.env_vars
    };
  }
}

/**
 * Port checker utility
 */
class PortChecker {
  /**
   * Check if a port is available
   * @param {number} port - Port to check
   * @param {string} host - Host to bind to
   * @returns {Promise<boolean>} Whether port is available
   */
  static async isPortAvailable(port, host = 'localhost') {
    return new Promise((resolve) => {
      const net = require('net');
      const server = net.createServer();

      server.listen(port, host, () => {
        server.once('close', () => {
          resolve(true);
        });
        server.close();
      });

      server.on('error', () => {
        resolve(false);
      });
    });
  }

  /**
   * Find available port from list
   * @param {number[]} ports - List of ports to check
   * @param {string} host - Host to bind to
   * @returns {Promise<number|null>} First available port or null
   */
  static async findAvailablePort(ports, host = 'localhost') {
    for (const port of ports) {
      if (await PortChecker.isPortAvailable(port, host)) {
        return port;
      }
    }
    return null;
  }
}

/**
 * Environment detector class
 */
class EnvironmentDetector {
  constructor() {
    this.cachedInfo = null;
    this.cacheExpiry = null;
    this.cacheTimeout = 60000; // 1 minute cache
  }

  /**
   * Get environment information (cached)
   * @param {boolean} forceRefresh - Force refresh of cached data
   * @returns {Promise<EnvironmentInfo>} Environment information
   */
  async detectEnvironment(forceRefresh = false) {
    const now = Date.now();

    // Use cached data if still valid
    if (!forceRefresh && this.cachedInfo && this.cacheExpiry && now < this.cacheExpiry) {
      return this.cachedInfo;
    }

    const envInfo = new EnvironmentInfo();

    try {
      // Detect configuration directories
      await this.detectConfigDirectories(envInfo);

      // Detect log directories
      await this.detectLogDirectories(envInfo);

      // Detect network configuration
      await this.detectNetworkConfiguration(envInfo);

      // Detect environment variables
      this.detectEnvironmentVariables(envInfo);

      // Cache the results
      this.cachedInfo = envInfo;
      this.cacheExpiry = now + this.cacheTimeout;

      logger.debug('Environment detection completed', envInfo.toJSON());

    } catch (error) {
      throw ErrorHandler.handle(error, { operation: 'environment_detection' });
    }

    return envInfo;
  }

  /**
   * Detect configuration directories and accessibility
   * @param {EnvironmentInfo} envInfo - Environment info to update
   */
  async detectConfigDirectories(envInfo) {
    const configDir = getConfigDir();
    const altConfigDir = getAlternativeConfigDir();
    const configFileName = ENVIRONMENT_DEFAULTS.config_filename;

    envInfo.config_dir = configDir;

    // Try primary config directory
    if (ensureDirectory(configDir)) {
      envInfo.config_dir_writable = true;
      const configPath = path.join(configDir, configFileName);
      envInfo.config_file_exists = fs.existsSync(configPath);
    } else {
      // Try alternative directory
      if (ensureDirectory(altConfigDir)) {
        envInfo.config_dir = altConfigDir;
        envInfo.config_dir_writable = true;
        const configPath = path.join(altConfigDir, configFileName);
        envInfo.config_file_exists = fs.existsSync(configPath);
      }
    }
  }

  /**
   * Detect log directories and accessibility
   * @param {EnvironmentInfo} envInfo - Environment info to update
   */
  async detectLogDirectories(envInfo) {
    // Log directory detection is handled by platform utils
    const { getLogDir } = require('./platform');
    const logDir = getLogDir();

    envInfo.log_dir = logDir;
    envInfo.log_dir_writable = ensureDirectory(logDir);
  }

  /**
   * Detect network configuration
   * @param {EnvironmentInfo} envInfo - Environment info to update
   */
  async detectNetworkConfiguration(envInfo) {
    // Check default port availability
    const defaultPort = ENVIRONMENT_DEFAULTS.default_port;
    const fallbackPorts = ENVIRONMENT_DEFAULTS.fallback_ports;

    const availablePort = await PortChecker.findAvailablePort([defaultPort, ...fallbackPorts]);
    envInfo.http_port_available = availablePort || defaultPort;
  }

  /**
   * Detect relevant environment variables
   * @param {EnvironmentInfo} envInfo - Environment info to update
   */
  detectEnvironmentVariables(envInfo) {
    const relevantVars = [
      'NODE_ENV',
      'VA_LOG_LEVEL',
      'VA_CONFIG_DIR',
      'VA_LOG_DIR',
      'VA_PORT',
      'VA_TIMEOUT',
      'HTTP_PROXY',
      'HTTPS_PROXY',
      'NO_PROXY',
      'PATH',
      'HOME',
      'USERPROFILE',
      'APPDATA',
      'LOCALAPPDATA'
    ];

    relevantVars.forEach(varName => {
      const value = process.env[varName];
      if (value !== undefined) {
        envInfo.env_vars[varName] = value;
      }
    });
  }

  /**
   * Validate environment requirements
   * @param {EnvironmentInfo} envInfo - Environment information
   * @returns {Object} Validation result with warnings and errors
   */
  validateEnvironment(envInfo) {
    const validation = {
      valid: true,
      warnings: [],
      errors: [],
      recommendations: []
    };

    // Check Node.js version
    const nodeVersion = parseInt(envInfo.node_version.slice(1).split('.')[0]);
    if (nodeVersion < 16) {
      validation.errors.push('Node.js version 16 or higher is required');
      validation.valid = false;
    }

    // Check memory
    const memoryGB = envInfo.memory_total / (1024 * 1024 * 1024);
    if (memoryGB < 0.5) {
      validation.warnings.push('Low memory detected (< 512MB), performance may be affected');
      validation.recommendations.push('Consider upgrading to at least 1GB RAM');
    }

    // Check configuration directory
    if (!envInfo.config_dir_writable) {
      validation.errors.push('Configuration directory is not writable');
      validation.valid = false;
    }

    // Check log directory
    if (!envInfo.log_dir_writable) {
      validation.warnings.push('Log directory is not writable, file logging disabled');
    }

    // Check port availability
    if (!envInfo.http_port_available) {
      validation.errors.push('No available HTTP ports found');
      validation.valid = false;
    }

    return validation;
  }
}

/**
 * Fallback configuration generator
 */
class FallbackConfigGenerator {
  /**
   * Generate fallback configuration based on environment
   * @param {EnvironmentInfo} envInfo - Environment information
   * @returns {Object} Fallback configuration
   */
  static generate(envInfo) {
    const config = {
      version: '1.0.0',
      current_provider_model: 'tencent-hunyuan-vision',
      providers: {
        tencent: {
          base: 'https://api.hunyuan.cloud.tencent.com',
          api_key: '',
          models: [
            {
              id: 'hunyuan-vision',
              name: 'Hunyuan Vision',
              type: 'vision',
              capabilities: ['image_analysis', 'ui_analysis', 'design_tokens'],
              max_tokens: 4096,
              supports_vision: true,
              enabled: true
            }
          ]
        }
      },
      http_server: {
        port: envInfo.http_port_available || ENVIRONMENT_DEFAULTS.default_port,
        auto_start: true,
        host: 'localhost'
      },
      logging: {
        level: process.env.VA_LOG_LEVEL || 'info',
        retention_days: 30,
        file_enabled: envInfo.log_dir_writable,
        console_enabled: true
      },
      mcp: {
        server_name: 'vision-analyzer',
        server_version: '1.0.0',
        timeout: ENVIRONMENT_DEFAULTS.timeout_default
      },
      features: {
        auto_model_selection: true,
        cache_analysis: memoryGB => memoryGB > 1, // Disable caching on low memory systems
        debug_mode: process.env.NODE_ENV === 'development'
      }
    };

    // Adjust features based on available memory
    const memoryGB = envInfo.memory_total / (1024 * 1024 * 1024);
    config.features.cache_analysis = config.features.cache_analysis(memoryGB);

    // Adjust timeouts based on system performance
    if (memoryGB < 1) {
      config.mcp.timeout = 60000; // Longer timeout for slower systems
    }

    return config;
  }

  /**
   * Save fallback configuration
   * @param {EnvironmentInfo} envInfo - Environment information
   * @param {Object} config - Configuration to save
   * @returns {Promise<string>} Path to saved configuration file
   */
  static async saveFallbackConfig(envInfo, config) {
    const configPath = path.join(envInfo.config_dir, ENVIRONMENT_DEFAULTS.config_filename);

    try {
      fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
      logger.info(`Fallback configuration saved to ${configPath}`);
      return configPath;
    } catch (error) {
      throw ErrorFactory.filesystem(`Failed to save fallback configuration: ${error.message}`, configPath, 'write');
    }
  }
}

// Global environment detector instance
const environmentDetector = new EnvironmentDetector();

/**
 * Detect current environment
 * @param {boolean} forceRefresh - Force refresh of cached data
 * @returns {Promise<EnvironmentInfo>} Environment information
 */
async function detectEnvironment(forceRefresh = false) {
  return await environmentDetector.detectEnvironment(forceRefresh);
}

/**
 * Validate environment and provide recommendations
 * @param {boolean} forceRefresh - Force environment re-detection
 * @returns {Promise<Object>} Validation results
 */
async function validateEnvironment(forceRefresh = false) {
  const envInfo = await detectEnvironment(forceRefresh);
  return environmentDetector.validateEnvironment(envInfo);
}

/**
 * Generate and save fallback configuration if needed
 * @param {boolean} force - Force generation even if config exists
 * @returns {Promise<Object>} Generated configuration
 */
async function ensureFallbackConfig(force = false) {
  const envInfo = await detectEnvironment();

  if (!force && envInfo.config_file_exists) {
    // Load existing configuration
    const configPath = path.join(envInfo.config_dir, ENVIRONMENT_DEFAULTS.config_filename);
    try {
      const content = fs.readFileSync(configPath, 'utf8');
      return JSON.parse(content);
    } catch (error) {
      logger.warn('Failed to load existing configuration, generating fallback', { error: error.message });
    }
  }

  const config = FallbackConfigGenerator.generate(envInfo);
  await FallbackConfigGenerator.saveFallbackConfig(envInfo, config);

  return config;
}

module.exports = {
  EnvironmentInfo,
  EnvironmentDetector,
  PortChecker,
  FallbackConfigGenerator,
  detectEnvironment,
  validateEnvironment,
  ensureFallbackConfig,
  ENVIRONMENT_DEFAULTS
};